/*
 * Copyright 2022 The Open Islands Authors. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.openislands.oi.config;

import org.openislands.oi.config.datasource.DBPropertiesParser;
import org.openislands.oi.constant.Dict;
import org.openislands.oi.util.ConfiguratorUtils;
import org.openislands.oi.util.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.env.OriginTrackedMapPropertySource;
import org.springframework.boot.env.YamlPropertySourceLoader;
import org.springframework.core.env.PropertySource;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.DefaultPropertySourceFactory;
import org.springframework.core.io.support.EncodedResource;

import java.io.IOException;
import java.util.*;

public class YamlPropertySourceFactory extends DefaultPropertySourceFactory {
    private static final Logger log = LoggerFactory.getLogger(YamlPropertySourceFactory.class);

    @Override
    public PropertySource<?> createPropertySource(String name, EncodedResource resource) throws IOException {
        YamlPropertySourceLoader yamlPropertySourceLoader = new YamlPropertySourceLoader();
        final Resource localResource = resource.getResource();
        final String filename = localResource.getFilename();

        // load class path. default
        List<PropertySource<?>> sources = yamlPropertySourceLoader.load(filename, localResource);
        OriginTrackedMapPropertySource classSource = ((OriginTrackedMapPropertySource) sources.get(0));
        final Map<String, Object> load = new LinkedHashMap<>(classSource.getSource());

        // load config path
        final Properties systemProperties = System.getProperties();
        String config = systemProperties.getProperty(Dict.OI_EXTERNAL_CONFIG);
        Resource configResource;
        if (StringUtils.noTrimEmpty(config) && (configResource = new FileSystemResource(config + "/" + filename)).exists()) {
            List<PropertySource<?>> sources2 = yamlPropertySourceLoader.load(filename, configResource);
            if (sources2.size() > 0) {
                OriginTrackedMapPropertySource configSource = ((OriginTrackedMapPropertySource) sources2.get(0));
                load.putAll(configSource.getSource());
            }
        }

        OriginTrackedMapPropertySource source =
                new OriginTrackedMapPropertySource(filename, Collections.unmodifiableMap(load), true);
        if (Dict.COMMON_CONFIG_FILE.equals(filename)) {
            // database
            Properties properties = ConfiguratorUtils.getProperties();
            properties.putAll(source.getSource());
            DBPropertiesParser.load(source.getProperty("database.type"), properties, systemProperties);
            // service exclusive
            final String[] propertyNames = source.getPropertyNames();
            // get service config by service name startswith
            String startsWith = name + ".";
            for (String propertyName : propertyNames) {
                if (propertyName.startsWith(startsWith)) {
                    final Object property = source.getProperty(propertyName);
                    systemProperties.put(propertyName.substring(startsWith.length()), property);
                }
            }
            // http port
            final Object value = Objects.requireNonNull(systemProperties.get("http_port"), "http_port is null");
            systemProperties.put("server.port", value);
        }

        checkConfig(name, filename, source);
        log.info("config {}, resource: {}, source: {}", name, resource, source.getSource());
        return source;
    }

    private void checkConfig(String service, String filename, PropertySource<?> source) {
        String prefix = service + " configuration file " + filename + " ";
        if (Dict.COMMON_CONFIG_FILE.equals(filename)) {
            Object nodeId = source.getProperty("node.node_id");
            if (nodeId == null || StringUtils.isBlank(nodeId.toString()))
                throw new IllegalStateException(prefix + "no required item node.node_id");
        }
    }
}